package com.surfilter.msmp.common.util.httpclient;

import com.surfilter.msmp.common.util.DomainUtil;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.*;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.cookie.*;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.DefaultCookieSpec;
import org.apache.http.impl.cookie.DefaultCookieSpecProvider;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.nio.charset.CodingErrorAction;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;

import static org.apache.http.protocol.HTTP.USER_AGENT;

/**
 * 单例 httpclient 实现.
 * 对多线程调用安全
 * @well
 * @version 4.1
 * @date 2013-10-14
 *
 */
public class HttpClient43Fetch {
	
	private static Logger logger = LoggerFactory.getLogger(HttpClient43Fetch.class);
	private static ScheduledExecutorService monitorExecutor;
	/**
	 * cache proxy server list
	 * */
//	private static final Map<String, HttpHost> httpProxy = new ConcurrentHashMap<String, HttpHost>();

//	private static final Map<String, InetSocketAddress> socketProxy = new ConcurrentHashMap<String, InetSocketAddress>();

    //管理所有的HttpClients对象，同一一个域名被多次请求时，客户端可复用
	public static Map<String, CloseableHttpClient> httpClients = new ConcurrentHashMap<>();

	private static Registry<ConnectionSocketFactory> register;

	static {
		try {
			TrustManager[] trustAllCerts = new TrustManager[1];
			TrustManager tm = new TrustAllCertsTM();
			trustAllCerts[0] = tm;


			SSLContext sslContext = SSLContexts.custom().build();
			sslContext.init(null, trustAllCerts, null);
			SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

			register = RegistryBuilder.<ConnectionSocketFactory>create()
	            .register("http", PlainSocksProxyConnectionSocketFactory.INSTANCE)
	            .register("https", sslSocketFactory)
	            .build();
		} catch (Exception ke) {
			logger.error("初始化 SSL 失败.", ke);
		}
	}

	// 因为是频繁访问不同主机的主页，属于短连接，不需要池化管理
	private static final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(register);

	private static final IdleConnectionMonitorThread idle;

	static {
		// Create socket configuration
		SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true)
				.setSoReuseAddress(true)
				.setSoLinger(HttpClientConstant.SOLINGER_TIMEOUT)
				.setSoTimeout(HttpClientConstant.SO_TIMEOUT)//读取数据超时必须设置
				.build();
//		manager.setDefaultSocketConfig(socketConfig);

		// Create message constraints
        MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(2000).build();

        // Create connection configuration
        ConnectionConfig connectionConfig = ConnectionConfig.custom()
            .setMalformedInputAction(CodingErrorAction.IGNORE)
            .setUnmappableInputAction(CodingErrorAction.IGNORE)
            .setCharset(Consts.UTF_8)
            .setMessageConstraints(messageConstraints)
            .build();

//		manager.setDefaultConnectionConfig(connectionConfig);
		//控制并发量的，两个都要设置
//		manager.setMaxTotal(HttpClientConstant.MAX_TOTAL);
//		manager.setDefaultMaxPerRoute(HttpClientConstant.MAX_PER_ROUTE);

		// monitor http connection release
		idle = new IdleConnectionMonitorThread(manager);
		idle.setDaemon(true);
		idle.start();

		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				idle.shutdown();
			}
		});
	}
	
	// make it single
	private HttpClient43Fetch() {
	}
	
	/**
	 * 连接存活策略
	 * */
	public static ConnectionKeepAliveStrategy keepAliveStrategy() {
		ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
	        @Override
			public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
	            // Honor 'keep-alive' header
	            HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
	            while (it.hasNext()) {
	                HeaderElement he = it.nextElement();
	                String param = he.getName();
	                String value = he.getValue();
	                
	                if (value != null && param.equalsIgnoreCase("timeout")) {
	                    try {
							return Long.parseLong(value) * 1000;
	                    } catch(NumberFormatException ignore) {
	                    }
	                }
	            }
	            return 60 * 1000;
	        }
	    };
	    return myStrategy;
	}
	
	/**
	 * 发送请求时设置压缩参数
	 * */
	public static HttpRequestInterceptor requestByGzip() {
		return new HttpRequestInterceptor() {
            @Override
			public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {
            	if(request.getHeaders(HttpClientConstant.NO_GZIP_FLAG) != null &&  request.getHeaders(HttpClientConstant.NO_GZIP_FLAG).length > 0) {
            		context.setAttribute(HttpClientConstant.NO_GZIP_FLAG, "true");
            		return;
            	}
            	
                if (!request.containsHeader(HttpClientConstant.ACCEPT_ENCODEING)) {
                    request.addHeader(HttpClientConstant.ACCEPT_ENCODEING, HttpClientConstant.ACCEPT_ENCODEING_GZIP);
                }
            }};
	}
	
	/**
	 * 对响应的压缩内容做解压
	 * */
	public static HttpResponseInterceptor responseByGzip() {
		return new HttpResponseInterceptor() {
            @Override
			public void process(
                    final HttpResponse response,
                    final HttpContext context) throws HttpException, IOException {
            	if(context.getAttribute(HttpClientConstant.NO_GZIP_FLAG) != null) {
            		return;
            	}
            	final HttpEntity entity = response.getEntity();
                // entity can be null in case of 304 Not Modified, 204 No Content or similar
                // check for zero length entity.
                if (entity != null && entity.getContentLength() != 0) {
                    final Header ceheader = entity.getContentEncoding();
                    if (ceheader != null) {
                        final HeaderElement[] codecs = ceheader.getElements();
                        boolean uncompressed = false;
                        for (final HeaderElement codec : codecs) {
                            final String codecname = codec.getName().toLowerCase(Locale.US);
                            if ("gzip".equals(codecname) || "x-gzip".equals(codecname)) {
                                response.setEntity(new GzipDecompressingEntity(response.getEntity()));
                                uncompressed = true;
                                break;
                            } else if ("deflate".equals(codecname)) {
                                response.setEntity(new DeflateDecompressingEntity(response.getEntity()));
                                uncompressed = true;
                                break;
                            } else if ("identity".equals(codecname)) {
                                /* Don't need to transform the content - no-op */
                                return;
                            } else {
                                throw new HttpException("Unsupported Content-Coding: " + codec.getName());
                            }
                        }
                        if (uncompressed) {
                            response.removeHeaders("Content-Length");
                            response.removeHeaders("Content-Encoding");
                            response.removeHeaders("Content-MD5");
                        }
                    }
                }
            }
        };
	}

	// 连接失败后重试策略
	public static HttpRequestRetryHandler retryHandler() {
		return new HttpRequestRetryHandler() {
			@Override
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
				//默认3次，修改为只要一次失败就丢弃
				if (executionCount >= 1) {
					// Do not retry if over max retry count
					return false;
				}
				if (exception instanceof NoHttpResponseException) {
					// Retry if the server dropped connection on us
					return false;
				}
				if (exception instanceof SSLHandshakeException) {
					// Do not retry on SSL handshake exception
					return false;
				}
				HttpRequest request = (HttpRequest) context.getAttribute(HttpCoreContext.HTTP_REQUEST);
				boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
				if (idempotent) {
					// Retry if the request is considered idempotent
					return true;
				}
				return false;
			}
		};
	}
	
	/**
	 * 明文 http 代理用户名密码授权
	 */
	private static CredentialsProvider credentialsProvider(String userName, String password) {
		CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
		credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
		return credentialsProvider;
	}
	
	/**
	 * 为每次 http 请求设置请求上下文, 并设置代理.
	 * 根据代理类型设置代理.
	 */
	private static HttpClientContext initRequestContext(HttpUriRequest httpUriRequest, Proxy proxy, int timeout) {
		HttpClientContext context = HttpClientContext.create();
		HttpHost httpHost = null;
		
//		String host = "";
//		if(proxy != null) {
//			host = proxy.getIp() + ":" + proxy.getPort();
//		}
		
		if(proxy != null && proxy.isHttpProxy()) {
			if (proxy.isNeedValidate()) {
				context.setCredentialsProvider(credentialsProvider(proxy.getUserName(), proxy.getPassword()));
			}

			//因为频繁发包，去掉代理缓存
//			httpHost = httpProxy.get(host);
//			if(httpHost == null) {
//				httpHost = new HttpHost(proxy.getIp(), proxy.getPort());
//				httpProxy.put(host, httpHost);
//			}

			httpHost = new HttpHost(proxy.getIp(), proxy.getPort());
		}
		
		RequestConfig requestConfig = RequestConfig.custom()
				.setSocketTimeout(HttpClientConstant.SO_TIMEOUT)
				.setConnectTimeout(timeout)
				.setConnectionRequestTimeout(timeout)
				.setProxy(httpHost)
				.build();
		
		if(httpUriRequest instanceof HttpGet) {
			((HttpGet) httpUriRequest).setConfig(requestConfig);
		} else if(httpUriRequest instanceof HttpPost) {
			((HttpPost) httpUriRequest).setConfig(requestConfig);
		} else if(httpUriRequest instanceof HttpHead) {
			((HttpHead) httpUriRequest).setConfig(requestConfig);
		}
		int randomint = HttpClientConstant.RANDOM.nextInt(HttpClientConstant.USER_AGENT_LENGTH);

		//获取一个随机用户代理
		httpUriRequest.addHeader(USER_AGENT, HttpClientConstant.HEAD_USER_AGENT[randomint]);

		//去掉代理主机，暂时用不到
//		if(proxy != null && !proxy.isHttpProxy()) {
//			InetSocketAddress socketAddr = socketProxy.get(host);
//			if(socketAddr == null) {
//				socketAddr = new InetSocketAddress(proxy.getIp(), proxy.getPort());
//				socketProxy.put(host, socketAddr);
//			}
//
//			context.setAttribute(HttpClientConstant.SOCKS_ADDRESS, socketAddr);
//			if (proxy.isNeedValidate()) {
//				Authenticator.setDefault(new SocksAuthenticator(proxy.getUserName(), proxy.getPassword()));
//			}
//		}
		
		return context;
	}
	
	public static CloseableHttpResponse execute(HttpUriRequest httpUriRequest) throws ClientProtocolException, IOException  {
		return execute(httpUriRequest, null);
	}
	
	public static CloseableHttpResponse execute(HttpUriRequest httpUriRequest, int timeout) throws ClientProtocolException, IOException  {
		return execute(httpUriRequest, null, timeout);
	}
	
	public static CloseableHttpResponse execute(HttpUriRequest httpUriRequest, Proxy proxy) throws ClientProtocolException, IOException {
		return execute(httpUriRequest, proxy, HttpClientConstant.CONNECT_TIMEOUT);
	}
	
	public static CloseableHttpResponse execute(HttpUriRequest httpUriRequest, Proxy proxy, int timeout) throws ClientProtocolException, IOException {
		HttpClientContext httpClientContext = initRequestContext(httpUriRequest, proxy, timeout);
		String domain = DomainUtil.getSite(httpUriRequest.getURI().toString());
		return getHttpClient(domain).execute(httpUriRequest, httpClientContext);
	}

	/**
	 * 创建HttpClient方法
	 * */
	private static CloseableHttpClient getHttpClient() {
		CookieSpecProvider easySpecProvider = new CookieSpecProvider() {
			@Override
			public CookieSpec create(HttpContext context) {
				return new DefaultCookieSpec(){
					@Override
					public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException {
						//
					}
				};
			}
		};
		Registry<CookieSpecProvider> cookieReg = RegistryBuilder.<CookieSpecProvider> create()
				.register(CookieSpecs.STANDARD, new DefaultCookieSpecProvider())
				.register("easy", easySpecProvider).build();
		BasicCookieStore cookieStore = new BasicCookieStore();

		return HttpClients
				.custom()
				.setKeepAliveStrategy(keepAliveStrategy())
				.addInterceptorFirst(requestByGzip())
				.addInterceptorFirst(responseByGzip())
				.setRetryHandler(retryHandler())		//请求失败后尝试重试
//				.setConnectionManager(manager)
				.setDefaultCookieStore(cookieStore)
				.setDefaultCookieSpecRegistry(cookieReg)
				.disableContentCompression()
				.build();
	}

	private static final byte[] objLock = new byte[0];

	private static CloseableHttpClient getHttpClient(String domain) {
		CloseableHttpClient httpClient = httpClients.get(domain);
		if (httpClient == null) {
			synchronized (objLock) {
				httpClient = getHttpClient();
//				httpClients.put(domain, httpClient);
			}
			}
		return httpClient;
	}

	/**
	 * 手动销毁制定域名http客户端
	 * */
	public static void destoryDomain(String domain){
			if (httpClients.containsKey(domain)) {
				try {
					httpClients.get(domain).close();
				} catch (IOException e) {
					logger.error("httpClient关闭失败");
				}finally {
					httpClients.remove(domain);
				}
			}
	}
}
